home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-06-25 | 45.5 KB | 925 lines | [TEXT/CCL ] |
- ; (c) Copyright 1990 by University of Massachusetts. All rights reserved.
- ; This software was conceived, designed, and written by Dan Suthers
- ; while supported by the National Science Foundation under grant number
- ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
- ; CA. Partial support was also received from the Office of Naval Research
- ; under a University Research Initiative Grant, contract N00014-86-K-0764.
- ; Mr. Suthers created this software under his own initiative while in an
- ; academic relationship with the University of Massachusetts. The above
- ; copyright notice was a condition placed by University lawyers on approval
- ; of distribution of this software by Apple Computer, and is not meant to
- ; imply that this software was created in an employment or "work for hire"
- ; relationship between the University and Mr. Suthers.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; File: Rule-Back.lisp
- ; Author: Dan Suthers
- ; Created: 19-Oct-88 21:57:32
- ; Modified: 22-Jun-90 02:24:58 (Dan Suthers)
- ; Language: Common Lisp
- ; Package: RULE
- ;
- ; Description: Rule-based reasoner built on the pattern matching facilities
- ; of DNET. Supports forward and backward reasoning.
- ;
- ; This file contains only code for back chaining. See also
- ; Rule-Defs, Rule-Build, and Rule-Forward.
- ; File RULES has documentation.
- ;
- ; (c) Copyright 1988, by Daniel D. Suthers
- ; Department of Computer and Information Science
- ; University of Massachusetts
- ; Amherst, Massachusetts 01003
- ;
- ; This software was conceived, designed, and written by Dan Suthers
- ; while supported by the National Science Foundation under grant number
- ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
- ; CA. Partial support was also received from the Office of Naval Research
- ; under a University Research Initiative Grant, contract N00014-86-K-0764.
- ; I wish to acknowledge the generous support of Beverly Woolf, who obtained
- ; the above grants and encouraged me to pursue my own research interests in
- ; her lab. This work would not have been possible without the resources and
- ; stimulating environment of the Computer and Information Science department.
- ;
- ; Permission to use, modify, and distribute this software is granted subject
- ; to the following restrictions and understandings:
- ; 1. The file header, including this notice, shall be retained, and may be
- ; extended to include documentation of modifications to the software.
- ; 2. This material is for nonprofit educational and research purposes only.
- ; Users are requested, but not required, to inform Mr. Suthers of any
- ; noteworthy uses of this software.
- ; 3. Mr. Suthers and the University of Massachusetts make no warrantee or
- ; representation that the operation of this software will be error free,
- ; and are under no obligation to provide any services.
- ; 4. Any user of such software agrees to indemnify and hold harmless Mr.
- ; Suthers and the University of Massachusetts from all claims arising
- ; out of the use or misuse of this software, or arising out of any
- ; accident, injury, or damage whatsoever, and from all costs, counsel
- ; fees, and liabilities incurred in or about any such claim, action, or
- ; proceeding brought thereon.
- ; 5. All materials and reports developed as a consequence of the use of
- ; this software shall duly acknowledge such use, in accordance with
- ; the usual standards of acknowledging credit in academic research.
- ;
- ; Status: Working. Could use efficiency hacks and added functionality.
- ;
- ; Changes:
- ; 16-Dec-88 To prevent circularity, backchaining not allowed on a rule
- ; with the same bindings already active.
- ; 26-Dec-88 Multiple changes to deal with bug in SUPPORT, which was not
- ; substituting subgoal bindings into consequent match bindings, with
- ; the result that :BIND in an :AND tried to evaluate an unbound variable.
- ; 27-Mar-89 Total rewrite. Eliminated redundant functions and reasoning;
- ; Added SUPPORTED function and :SEQ operator; Near Miss processing of
- ; :AND; and various bugs fixed.
- ; 13-Apr-89 Use of dnet::match-internal and dnet:unify replaced with
- ; match-rules and fast-bind, to take advantage of what is known about
- ; matching rules to speed things up.
- ; 26-Apr-89 Fixed :OR support tree (which was improperly adjoining arcs).
- ; 19-Sep-89 Fixed :LISP SUPPORT bug: TRJ-ARC-MODALITY made all :LISPs
- ; supported, regardless of outcome.
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- ; To Do:
- ; Improve datum-justification breakdown for conjunctive datum.
- ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- ; Program Notes
- ;
- ; On Transitive Bindings:
- ;
- ; At one point I thought I needed a version of SUBSTITUTE-BINDINGS, called
- ; SUBSTITUTE-TRANSITIVE-BINDINGS, which iterated the substitution until no
- ; more occurred. This was to deal with ((?:x . ?:y) (?:y . <constant>)).
- ; While UNIFY can return such a binding set, I now believe the backchaining
- ; code will not allow it. This arose only because SUPPORT was APPENDING
- ; the bindings that matched a goal to a consequent (yielding (?:x . ?:y))
- ; with those by which the antecedent subgoal was satisfied ((?:y . <constant>)),
- ; to give the above "transitive" binding set. This was a mistake: now SUPPORT
- ; works like RETRIEVE by SUBSTITUTING the latter bindings into the former,
- ; to yield ((?:x . <constant>)). Hence, given there are no variables in the
- ; data base, direct retrieval from the database cannot produce transitive
- ; bindings, and as I've just shown the rule chainer will eliminate all such
- ; bindings it produces.
- ;
- ; On Finding the First Result:
- ;
- ; I had a parameter :FIND-ALL for the RETRIEVE function. If NIL, I'd stop
- ; at the first result found. This does not work. You don't know which way
- ; of getting a subgoal will yield bindings consistent with later subgoals,
- ; so you need to return all of them for use by other retrieval functions.
- ; If you want only some result, you could save consing by writing a version
- ; of RETRIEVE which only does substitute-bindings to get the results for
- ; one of the binding sets returned from RETRIEVE-BINDINGS.
- ;
- ;
- ; On assumptions enabling FAST-BIND:
- ;
- ; This replaces DNET:UNIFY when processing result of dnet::match-links
- ; for retrieval of backchaining rules. We rely on what we know about
- ; this matching to use a faster function:
- ;
- ; 1. The two patterns will always correspond element-wise, i.e. atomic
- ; non-variables have already been verified to be equal by match-links,
- ; each sublist has the same number of elements, etc. (We don't use
- ; dotted endings in rules.)
- ; 2. Variables in each pattern are assumed to be unique and are treated
- ; as such, even if they have the same name. (This can be done because
- ; we return two binding sets, one for each direction of binding.)
- ; In particular, ?:x in pattern-1 can be bound to ?:x in pattern-2
- ; because each occurance of ?:x is distinct, and we never have to check
- ; for whether a variable occurs inside an expression it is being bound
- ; to. This will never cause trouble because:
- ; a. Variables ARE unique symbols across rules, so the only way a
- ; symbol will occur in both patterns is if a rule re-invokes itself.
- ; b. We check for circular rule invocations by testing the entire
- ; binding set against previous sets on active-bindings.
- ; c. Hence the only time a variable symbol will occur in both patterns
- ; is when a rule re-invokes itself with some (but not all) bindings
- ; repeated, so a rule variable will be bound to itself by fast-bind.
- ; It is placed on only one binding list (the first one returned).
- ; 3. As a consequence of 1 and 2, the only way two patterns can fail to
- ; match is inconsistent binding of a variable in one direction. Other
- ; than checking for this, all we have to do is record the bindings.
- ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (in-package :RULE)
-
- (export '(
-
- *support-tree*
-
- retrieve
- support
-
- trj-arc
- trj-arc-p
- trj-arc-bindings
- trj-arc-grounds
- trj-arc-modality
- trj-arc-warrant
-
-
- trj-node
- trj-node-p
- trj-node-bindings
- trj-node-claim
- trj-node-modality
- trj-node-support
-
- ))
-
- (require :mappings)
- (require :rule-defs)
- #+:CCL (require :rule-browser)
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; DATA STRUCTURES
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (deftype boolean () '(or T null))
-
- (defstruct TRJ-NODE
- "Nodes of a tree representing the support generated by backchaining.
- CLAIM: an expression we are trying to support at this node.
- MODALITY: one of :SUPPORTED or :UNSUPPORTED.
- SUPPORT: list of TRJ-ARC structures, forming an implicit disjunction."
-
- (CLAIM nil :type list)
- (MODALITY :unsupported :type (member :supported :unsupported))
- (SUPPORT nil :type list))
-
- (defstruct TRJ-ARC
- "Each of these represents one way in which support for a TRJ-NODE has been
- attempted, by branching to supporting nodes.
- WARRANT: one of :ASSERTED, :AND, :SEQ, :OR, :LISP, :BIND, or a rule name.
- GROUNDS: a list, or a list of lists, of TRJ-NODEs, depending on the CLAIM.
- BINDINGS: a binding set or a list of binding sets.
- :AND, :SEQ for a TRJ-NODE claim of <c1> ... <cN> have grounds ((<g1> ... <gN>))
- and one binding set.
- :OR for claim <d1> ... <dN> has grounds ((<g1> ... <gN>)) and a list of binding
- sets.
- :BIND for <variable> <expression> has a single binding set binding only the
- <variable>, and <expression> as grounds.
- :LISP for <expression> has null binding set and a TRJ-NODE recording success
- or failure of the expression.
- <Rule> has (<g1> ... <gn>) grounds and a list of respective
- unifications between the consequent and the claim as bindings.
- :ASSERTED for <pattern> has <expression> as grounds and binding is the single
- binding set unifying the two."
-
- (WARRANT nil :type symbol)
- (GROUNDS nil :type list)
- (BINDINGS nil :type list))
-
- ;;; Ways to make nodes and arcs look like they have each other's fields.
-
- (defun TRJ-ARC-MODALITY (arc)
- "trj-arc-modality <arc> [Function]
- Returns :supported or :unsupported, depending on the arc's warrant and
- whether there are nodes on the arc's grounds which provide support via
- that warrant. (Eg. for :and, all grounds must be :supported; for :or or
- a rule, at least one must be.) No argument checking."
- (declare (optimize (safety 1) (space 2) (speed 3)))
- (case (trj-arc-warrant arc)
- ((:asserted :bind) :supported)
- ((:and :seq)
- (if (every #'(lambda (n) (declare (type trj-arc n))
- (eq (trj-node-modality n) :supported))
- (trj-arc-grounds arc))
- :supported
- :unsupported))
- ((:lisp)
- (if (eq (trj-node-modality (first (trj-arc-grounds arc))) :supported)
- :supported
- :unsupported))
- ;; Both :OR and rules are disjunctive (rule only has to succeed one way).
- (otherwise
- (if (some #'(lambda (n) (declare (type trj-arc n))
- (eq (trj-node-modality n) :supported))
- (trj-arc-grounds arc))
- :supported
- :unsupported))))
-
- (defun TRJ-NODE-BINDINGS (node)
- "trj-node-bindings <node> [Function]
- Returns a list of binding sets, which includes only the bindings by
- which <node> succeeds. No argument checking."
- (declare (optimize (safety 1) (space 2) (speed 3)))
- (mapcan #'(lambda (a) (declare (type trj-arc a))
- (if (eq (trj-arc-modality a) :supported)
- (list (trj-arc-bindings a))))
- (trj-node-support node)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; INTERNAL FUNCTIONS AND MACROS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;;; Little Helpers
-
- (eval-when (compile eval)
-
- (defmacro LISP-EVAL (forms)
- ;; Escape to LISP: Want to return result of the last form evaluated. No
- ;; bindings ever needed, since they're already substituted by backchaining.
- ;; Making this a macro in case I want to put trace or error protection in.
- `(eval (cons 'progn ,forms)))
-
- (defmacro SECOND-VALUE (multiple-value-call)
- ;; Return second of two values.
- `(multiple-value-bind
- (first-value second-value)
- ,multiple-value-call
- (declare (ignore first-value))
- second-value))
-
- (defmacro EXTEND-ACTIVE-BINDINGS (bindings1 bindings2 active-bindings)
- ;; Extends the active-bindings list tested by circular bindings.
- `(if (or ,bindings1 ,bindings2)
- (cons (cons ,bindings1 ,bindings2) ,active-bindings)
- ,active-bindings))
-
- (defmacro CIRCULAR-BINDINGS (bindings1 bindings2 active-bindings pattern-p)
- ;; Returns T iff the bindings indicate an active rule has directly or
- ;; indirectly reinvoked itself.
- `(if (or ,bindings1 ,bindings2)
- ;; There are bindings: see if they are active. Since variables
- ;; are unique to rules, don't have to index active-bindings by rule.
- (member (cons ,bindings1 ,bindings2) ,active-bindings :test #'equal)
- ;; Bindings NIL OK only if there are no variables in goal. Some
- ;; rules can generate subgoals with same variables as the rule's
- ;; consequent. These subgoals bind to consequent with NIL bindings.
- ,pattern-p))
-
- (defmacro TRACE-BACKWARD-RULE (goal rule-record bindings)
- ;; Print a trace of a backwards application of a rule, if turned on.
- `(if *rule-trace*
- #-:CCL (format *rule-trace* "~&B: ~S <-- ~S -- ~S"
- ,goal (rule-record-rule-name ,rule-record)
- (substitute-bindings ,bindings
- (rule-record-pattern ,rule-record)))
- #+:CCL (rule-trace "~&B: ~S <-- ~S -- ~S"
- ,goal (rule-record-rule-name ,rule-record)
- (substitute-bindings ,bindings
- (rule-record-pattern ,rule-record)))
- ))
-
- (defmacro MATCH-RULES (goal rules-dnet)
- ;; This imitates dnet::match-internal, the main difference being
- ;; the use of fast-bind instead of dnet:unify.
- `(let ((consequents nil) (consequent-bindings nil) (reverse-bindings nil))
- (declare (list consequents consequent-bindings reverse-bindings))
- (setf (cdr *consequent-template*) ,goal)
- (dolist (link (the list
- (dnet::match-links *consequent-template*
- (list (dnet::dnet-link
- (the dnet (sm:gets 'dnet ,rules-dnet)))))))
- (declare (list link))
- (multiple-value-bind
- (success binding-1 binding-2)
- (fast-bind *consequent-template*
- (dnet-terminal-expr (cdr link)) nil nil)
- (declare (symbol success) (list binding-1 binding-2))
- (when success
- (push (dnet-terminal-expr (cdr link)) consequents)
- (push binding-1 consequent-bindings)
- (push binding-2 reverse-bindings))))
- (values consequents consequent-bindings reverse-bindings)))
-
- ) ; eval-when
-
- ;;;-------------------------------------------------------------------
- ;;; Maintaining lists of support arcs.
-
- (defun MERGE-SUPPORT-ARCS (arcs1 arcs2)
- ;; Unions two lists of support arcs, merging arcs whose warrant and bindings
- ;; are the same, diving in recursively as needed.
- (dolist (arc arcs2 arcs1)
- (declare (type trj-arc arc))
- (setf arcs1 (adjoin-support-arc arc arcs1))))
-
- (defun ADJOIN-SUPPORT-ARC (arc arc-list)
- ;; Adds a support arc to a set of arcs. The arc is already a "member" if
- ;; an arc with the same warrant and bindings is. In that case, merge the
- ;; grounds recursively; else add the arc to the arc-list.
- (let* ((matching-arc
- (first (member arc arc-list
- :test #'(lambda (a1 a2)
- (declare (type trj-arc a1 a2))
- (and (eq (trj-arc-warrant a1)
- (trj-arc-warrant a2))
- (equal (trj-arc-bindings a1)
- (trj-arc-bindings a2))))))))
- (declare (type trj-arc arc))
- (cond (matching-arc
- (setf (trj-arc-grounds matching-arc)
- (merge-support-arcs (trj-arc-grounds matching-arc)
- (trj-arc-grounds arc)))
- arc-list)
- (t (setq arc-list (nconc arc-list (list arc)))))))
- ;;;(push arc arc-list)))))
-
- ;;;-------------------------------------------------------------------
- ;;; Special purpose unification. See program notes.
-
- (defun FAST-BIND (pattern-1 pattern-2 bindings-1 bindings-2)
- ;; Assumes that pattern-1 and pattern-2 correspond element wise, and
- ;; the only way the binding can fail is if one variable is bound more
- ;; than once, to different elements in the other pattern.
- (declare (list bindings-1 bindings-2)
- (optimize (safety 1) (space 2) (speed 3)))
- (cond ((variable-p pattern-1)
- (let ((prev-binding (assoc pattern-1 bindings-1)))
- (if prev-binding
- (if (equal (cdr prev-binding) pattern-2)
- (values t bindings-1 bindings-2)
- (values nil nil nil))
- (values t (cons (cons pattern-1 pattern-2) bindings-1) bindings-2))))
- ((variable-p pattern-2)
- (let ((prev-binding (assoc pattern-2 bindings-2)))
- (if prev-binding
- (if (equal (cdr prev-binding) pattern-1)
- (values t bindings-1 bindings-2)
- (values nil nil nil))
- (values t bindings-1 (cons (cons pattern-2 pattern-1) bindings-2)))))
- ;; If one is a non-variable atom, match-links guarantees the other is an
- ;; equal atom.
- ((atom pattern-1)
- (values t bindings-1 bindings-2))
- (T
- (multiple-value-bind
- (car-success extended-bindings-1 extended-bindings-2)
- (fast-bind (car pattern-1) (car pattern-2) bindings-1 bindings-2)
- (declare (list extended-bindings-1 extended-bindings-2))
- (if car-success
- (fast-bind (cdr pattern-1) (cdr pattern-2)
- extended-bindings-1 extended-bindings-2)
- (values nil nil nil))))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Backward Chaining
-
- ;;;------------------------------------------------------------------------
- ;;; Version which returns results like MATCH, without constructing support.
-
- (defun RETRIEVE-BINDINGS (goal data-dnet rules-dnet active-bindings)
- ;; Returns all binding sets by which <goal> is satisfied. Active-bindings
- ;; are currently in effect for backchained rules, used to avoid circularity.
- (declare (list goal active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
- (case (first goal)
- ((:and :seq)
- ;; These are treated the same by RETRIEVE, but not by SUPPORT.
- (retrieve-and (rest goal) data-dnet rules-dnet nil active-bindings))
- ((:or)
- (retrieve-or (rest goal) data-dnet rules-dnet active-bindings))
- ((:lisp)
- ;; Must signal success or failure based on whether result is T or nil.
- ;; Success is an empty binding set: (()); and failure is no binding set: ().
- (if (lisp-eval (rest goal)) (list nil) nil))
- ((:bind)
- ;; The form introduces a new variable. Assumes that all variables in
- ;; the form to evaluate have been bound. Return a set of one binding.
- (list (list (cons (second goal) (lisp-eval (cddr goal))))))
- (otherwise
- ;; Combine results from direct match to data-dnet with backchaining results.
- ;; Since only the bindings are needed, call PATTERN-MATCH-LINKS directly
- ;; instead of MATCH-PATTERN-INTERNAL: this requires duplicating some of the
- ;; latter's link processing code (see DNET).
- (nconc (mapcar #'(lambda (link) (declare (list link))
- (second-value
- (bind-vars goal (dnet-terminal-expr (cdr link)) nil)))
- (dnet::pattern-match-links
- goal (list (dnet::dnet-link (sm:gets 'dnet data-dnet)))))
- (retrieve-backchain goal data-dnet rules-dnet active-bindings)))))
-
- (defun RETRIEVE-BACKCHAIN (goal data-dnet rules-dnet active-bindings)
- ;; Returns bindings which result from backchaining on the goal.
- (declare (list goal active-bindings)
- (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
-
- ;; Find matching consequents. Iterate over each of their antecedents (recorded
- ;; in rule-records). This iteration is over an implicit disjunction, and each
- ;; consequent & antecedent pair may support new data independently of others.
- (multiple-value-bind
- (consequents consequent-bindings reverse-bindings)
- (match-rules goal rules-dnet)
- (declare (list consequents consequent-bindings reverse-bindings))
-
- ;; Loop for consequents matched, returning binding sets accumulated.
- (do ((cptr consequents (rest cptr))
- (bptr consequent-bindings (rest bptr))
- (rptr reverse-bindings (rest rptr))
- (goal-has-variables (pattern-p goal))
- (binding-sets (list :head)))
- ((null cptr) (rest binding-sets))
- (declare (list cptr bptr rptr binding-sets))
-
- ;; Loop for antecedents indexed under this consequent.
- (dolist (rule-record (dnet::expr-info-internal (first cptr) rules-dnet))
- (declare (list rule-record))
-
- ;; The following avoids circularity in rules by preventing repeated bindings.
- (unless (circular-bindings (first bptr) (first rptr)
- active-bindings goal-has-variables)
- (trace-backward-rule goal rule-record (first bptr))
-
- ;; Make appropriate subgoal expression by substituting bindings into
- ;; the current antecedent (the rule-record-pattern), and get bindings
- ;; which satisfy this subgoal. Then construct bindings to return by
- ;; substituting these subgoal bindings (which satisfied the antecedent)
- ;; into consequent bindings (by which the goal matched the backchaining
- ;; rule). Consequent bindings has cdr-references to rule variables
- ;; which are bound in the subgoal bindings, and hence replaced by this
- ;; substitution.
- (nconc binding-sets
- (mapcar #'(lambda (subgoal-binding)
- (declare (list subgoal-binding))
- (utils:compose-mappings (first bptr) subgoal-binding))
- (retrieve-bindings
- (substitute-bindings (first rptr)
- (rule-record-pattern rule-record))
- data-dnet rules-dnet
- (extend-active-bindings (first bptr) (first rptr) active-bindings)))))))))
-
- (defun RETRIEVE-AND (and-goals data-dnet rules-dnet previous-bindings active-bindings)
- ;; Return all sets of bindings satisfying an :AND or :SEQ expression.
- ;; This is tricky because we have to deal with both multiple ways to satisfy
- ;; each conjunct and ensuring that bindings are consistent across conjuncts.
- ;; (Previous-bindings are from earlier conjuncts in the :and expression, while
- ;; active-bindings are from active rules.)
- (declare (list and-goals previous-bindings active-bindings)
- (symbol data-dnet rules-dnet) (optimize (safety 1) (space 2) (speed 3)))
- (if (null and-goals)
- ;; Nothing left to do, but we expected the single previous-bindings to be
- ;; expanded into a set of its extensions, so list it to make a set.
- (list previous-bindings)
- ;; Find all ways to satisfy the first conjunct. (Previous bindings are assumed
- ;; to have already been substituted into remaining conjuncts, which guarantees
- ;; consistency.) Extend the previous bindings with each resulting extension
- ;; to the bindings, and recurse to consume remaining conjuncts, substituting
- ;; the appropriate bindings into the remaining conjuncts for each recursion.
- (mapcan #'(lambda (binding-extension)
- (declare (list binding-extension))
- ;; Recurse to consume remaining conjuncts, bound by each extension.
- ;; Note (append binding-extension previous-bindings) didn't work!
- (retrieve-and
- (substitute-bindings binding-extension (rest and-goals))
- data-dnet rules-dnet (append previous-bindings binding-extension)
- active-bindings))
- ;; All ways to satisfy the first conjunct.
- (retrieve-bindings
- (first and-goals) data-dnet rules-dnet active-bindings))))
-
- (defun RETRIEVE-OR (or-goals data-dnet rules-dnet active-bindings)
- (declare (list or-goals active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
- ;; Since disjunctive, can split into separate calls for each disjunct
- ;; and combine results with no worry about coordinating variables.
- ;; Don't bother checking for duplicates: that is done elsewhere.
- (mapcan #'(lambda (disjunct) (declare (list disjunct))
- (retrieve-bindings disjunct data-dnet rules-dnet active-bindings))
- or-goals))
-
- ;;;------------------------------------------------------------------------
- ;;; Version which returns support tree.
-
- (defun SUPPORT-ARCS (goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- ;; Dispatches analogous to REFERENCE-BINDINGS (see); Returns a list of arcs.
- (declare (list goal active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
- (case (first goal)
- ((:and) (support-and goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings))
- ((:seq) (support-seq goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings))
- ((:or) (support-or goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings))
- ((:lisp) (support-lisp (rest goal) record-failure))
- ((:bind) (support-bind (second goal) (rest (rest goal))))
- (otherwise (support-pattern goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings))))
-
- (defun SUPPORT-PATTERN (goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- ;; Returns list of arcs supporting (whether directly or by rule application)
- ;; a goal which doesn't start with an interpreted operator.
- (declare (list goal active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
- (let ((success nil) (support-arcs nil))
- (declare (list support-arcs))
- ;; Initialize support list to direct matches in data-dnet.
- (multiple-value-bind
- (results bindings) (dnet::match-pattern-internal goal data-dnet nil)
- (declare (list results bindings))
- (when results
- (setq success t)
- (do ((rptr results (rest rptr)) (bptr bindings (rest bptr)))
- ((null rptr))
- (declare (list rptr bptr))
- (if include-datum-just
- (setf support-arcs
- (nconc (datum-justification-arcs (first rptr) (first bptr) data-dnet)
- support-arcs))
- (push (make-trj-arc :warrant ':asserted
- :grounds (list (make-trj-node
- :claim (first rptr)
- :modality ':supported
- :support nil))
- :bindings (first bptr))
- support-arcs)))))
- ;; Get list of TRJ-ARCs to subtrees from back chaining and add these.
- (multiple-value-bind
- (backchaining-success backchaining-support-arcs)
- (support-backchain goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- (declare (list backchaining-support-arcs))
- (if backchaining-success (setq success t))
- ;; Prefer to keep the easier support up front; also preserve order.
- ;; (Do NOT need to test (or backchaining-success record-failure) here!)
- (setf support-arcs
- (merge-support-arcs support-arcs backchaining-support-arcs)))
- ;; All done.
- (values success support-arcs)))
-
- (defun SUPPORT-BACKCHAIN (goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- ;; Returns a success predicate and arcs to subtrees resulting from back chaining.
- (declare (list goal active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
-
- ;; Find matching consequents. Iterate over each of their antecedents (recorded
- ;; in rule-records). This iteration is over an implicit disjunction, and each
- ;; consequent & antecedent pair may support new data independently of others.
- (multiple-value-bind
- (consequents consequent-bindings reverse-bindings)
- (match-rules goal rules-dnet)
- (declare (list consequents consequent-bindings reverse-bindings))
-
- ;; Loop for consequents matched.
- (do ((cptr consequents (rest cptr))
- (bptr consequent-bindings (rest bptr))
- (rptr reverse-bindings (rest rptr))
- (goal-has-variables (pattern-p goal))
- (success nil) (support-arcs nil))
- ((null cptr) (values success support-arcs))
- (declare (list cptr bptr rptr support-arcs))
-
- ;; Loop for antecedents indexed under this consequent.
- (dolist (rule-record (dnet::expr-info-internal (first cptr) rules-dnet))
- (declare (list rule-record))
-
- ;; The following avoids circularity in rules by preventing repeated bindings.
- (unless (circular-bindings (first bptr) (first rptr)
- active-bindings goal-has-variables)
- (trace-backward-rule goal rule-record (first bptr))
-
- ;; Substitute bindings to construct subgoals, and substitute returned
- ;; bindings into goal/consequent bindings. See RETRIEVE-BACKCHAIN.
- (let ((subgoal (substitute-bindings (first rptr)
- (rule-record-pattern rule-record))))
- (declare (list subgoal))
- (multiple-value-bind
- (success-this-time new-support-arcs)
- (support-arcs
- subgoal data-dnet rules-dnet record-failure include-datum-just
- (extend-active-bindings (first bptr) (first rptr) active-bindings))
- (declare (list new-support-arcs))
- (if success-this-time (setq success t))
- (dolist (sa new-support-arcs)
- (declare (type trj-arc sa))
- (setf support-arcs
- (adjoin-support-arc
- (make-trj-arc
- :warrant (rule-record-rule-name rule-record)
- :grounds (list (make-trj-node
- :claim subgoal
- :modality (trj-arc-modality sa)
- :support (list sa)))
- :bindings (utils:compose-mappings (first bptr)
- (trj-arc-bindings sa)))
- support-arcs))))))))))
-
- ;;; We must return multiple arcs with warrant :AND supporting the goal. Each
- ;;; arc must have a consistently bound set of support for the conjuncts, and
- ;;; there may be many ways to satisfy the conjuncts. Conceptually, the idea
- ;;; is to find support for the first conjunct; then split this node into K nodes
- ;;; where K is the number of distinct ways to support it; then recurse to
- ;;; consume remaining conjuncts with each of K bindings substituted into them;
- ;;; then combine the lists of nodes returned.
-
- (defun SUPPORT-AND (and-goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- (declare (list and-goal active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
- ;; Just getting things started.
- (support-conjunction-arcs
- (make-trj-arc :warrant ':and :grounds nil :bindings nil)
- (rest and-goal) data-dnet rules-dnet
- record-failure include-datum-just active-bindings))
-
- ;;; :SEQ will be treated identically except short circuiting on failure.
-
- (defun SUPPORT-SEQ (and-goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- (declare (list and-goal active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
- ;; Just getting things started.
- (support-conjunction-arcs
- (make-trj-arc :warrant ':seq :grounds nil :bindings nil)
- (rest and-goal) data-dnet rules-dnet
- record-failure include-datum-just active-bindings))
-
- (defun SUPPORT-CONJUNCTION-ARCS (arc goals data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- ;; Takes an AND or :SEQ arc and some goals to be consumed, and returns list of
- ;; support arcs which constitute the "split" of the arc into its extensions to
- ;; include the remaining conjuncts.
- (declare (list goals active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
- (if (null goals)
- (let ((supported (eq (trj-arc-modality arc) ':supported)))
- (values supported (if (or supported record-failure) (list arc))))
-
- ;; Find all ways to satisfy the first conjunct. Previous bindings are assumed
- ;; to have already been substituted into remaining conjuncts, which guarantees
- ;; consistency. However, there may still be variables in the conjunct.
- (multiple-value-bind
- (first-conjunct-success support-arcs)
- (support-arcs (first goals) data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- (declare (list support-arcs))
- (if (or first-conjunct-success
- (and record-failure (eq (trj-arc-warrant arc) ':and)))
- ;; Split arc into list of arcs, each with extended support and bindings.
- (let ((split-arcs
- (if support-arcs
- (mapcar
- #'(lambda (sa) (declare (type trj-arc sa))
- (make-trj-arc
- :warrant (trj-arc-warrant arc)
- ;; Retain conjunct order. Reusing list elsewhere (no nconc).
- :grounds (append (trj-arc-grounds arc)
- (list (make-trj-node
- :claim (first goals)
- :modality (trj-arc-modality sa)
- :support (list sa))))
- :bindings (append (trj-arc-bindings arc)
- (trj-arc-bindings sa))))
- support-arcs)
- (list
- (make-trj-arc
- :warrant (trj-arc-warrant arc)
- :grounds (append (trj-arc-grounds arc)
- (list (make-trj-node
- :claim (first goals)
- :modality :unsupported
- :support nil)))
- :bindings (append (trj-arc-bindings arc) (list nil)))))))
- ;; Recurse on remaining conjuncts with each separate binding substituted
- ;; in, then combine results. (But skip failure arcs in :SEQ.)
- (do ((sa-ptr split-arcs (rest sa-ptr))
- (success nil) (results (list ':head)))
- ((null sa-ptr) (values success (rest results)))
- ;; If the conjunct just consumed is supported, ok to try remainder.
- ;; :AND lets us try even if it is not supported, to build rest of tree.
- (if (or (eq (trj-arc-modality (first sa-ptr)) ':supported)
- (eq (trj-arc-warrant arc) ':and)) ; record-failure must be true
- (multiple-value-bind
- (recursive-success recursive-support-arcs)
- (support-conjunction-arcs
- (first sa-ptr)
- (substitute-bindings (trj-arc-bindings (first sa-ptr))
- (rest goals))
- data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- (if recursive-success (setq success t))
- (when (or recursive-success record-failure)
- (nconc results recursive-support-arcs)))
- ;; Unsupported, operator :seq, and record-failure T:
- (nconc results
- (list (make-trj-arc
- :warrant (trj-arc-warrant arc)
- :grounds (append (trj-arc-grounds arc)
- (list (make-trj-node
- :claim (first goals)
- :modality :unsupported
- :support support-arcs)))
- :bindings (trj-arc-bindings arc)))))))
- (if record-failure ; it is :seq and failed
- (values nil
- (list
- (make-trj-arc
- :warrant :seq
- :grounds (append (trj-arc-grounds arc)
- (list (make-trj-node
- :claim (first goals)
- :modality :unsupported
- :support nil)))
- :bindings (trj-arc-bindings arc))))
- (values nil nil))
- ))))
-
- (defun SUPPORT-OR (or-goal data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- ;; Returns a list of a single TRJ arc, whose grounds correspond to disjuncts.
- (declare (list or-goal active-bindings) (symbol data-dnet rules-dnet)
- (optimize (safety 1) (space 2) (speed 3)))
- ;; Since disjunctive, can split into separate calls for each disjunct
- ;; and combine results with no worry about coordinating variables.
- ;; Don't bother checking for duplicates: that is done elsewhere.
- (do ((goal-ptr (cdr or-goal) (cdr goal-ptr))
- (supported nil)
- (grounds (list :head))
- (bindings (list :head)))
- ((null goal-ptr) (values supported
- (list (make-trj-arc :warrant :or
- :grounds (rest grounds)
- :bindings (rest bindings)))))
- (declare (list goal-ptr grounds bindings))
- (multiple-value-bind
- (success support-arcs)
- (support-arcs (first goal-ptr) data-dnet rules-dnet record-failure
- include-datum-just active-bindings)
- (declare (list support-arcs))
- (if success (setf supported t))
- (when (or success record-failure)
- (nconc grounds (list (make-trj-node
- :claim (first goal-ptr)
- :modality (if success :supported :unsupported)
- :support support-arcs)))
- (nconc bindings (list (mapcar #'trj-arc-bindings support-arcs)))))))
-
- (defun SUPPORT-LISP (forms record-failure)
- (declare (list forms) (optimize (safety 1) (space 2) (speed 3)))
- ;; Success depends on results; there are no bindings.
- (let ((success (lisp-eval forms)))
- (values success
- (if (or success record-failure)
- (list (make-trj-arc
- :warrant ':lisp
- :grounds (list (make-trj-node
- :claim forms
- :modality (if success :supported :unsupported)
- :support nil))
- :bindings nil))))))
-
- (defun SUPPORT-BIND (variable forms)
- (declare (symbol variable) (list forms)
- (optimize (safety 1) (space 2) (speed 3)))
- ;; Always succeeds; return an arc that provides bindings.
- (let ((result (lisp-eval forms)))
- (values t
- (list (make-trj-arc
- :warrant ':bind
- :grounds (list (make-trj-node
- :claim forms
- :modality ':supported
- :support nil))
- :bindings (list (cons variable result)))))))
-
- (defun DATUM-JUSTIFICATION-NODE (ground bindings data-dnet)
- (make-trj-node :claim ground
- :modality :supported
- :support (datum-justification-arcs ground bindings data-dnet)))
-
- (defun DATUM-JUSTIFICATION-ARCS (datum bindings data-dnet)
- (mapcar #'(lambda (datum-just)
- (make-trj-arc :warrant (justification-warrant datum-just)
- :grounds (list (datum-justification-node
- ;; unify may be bogus. More correct
- ;; to do it to the rule.
- (justification-grounds datum-just)
- (unify datum (justification-grounds datum-just))
- data-dnet))
- :bindings bindings))
- (datum-justification datum data-dnet)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;
- ;;; USER INTERFACE FUNCTIONS
- ;;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;;;------------------
- ;;; Backward Chaining
-
- (defun SUPPORTED (goal data rules)
- "supported <goal> <data> <rules> [Function]
- Returns non-NIL value iff <goal> is supported by <data> and <rules>."
- (declare (inline retrieve-bindings))
- (check-type data symbol)
- (check-type rules symbol)
- (assert (sm:gets 'dnet data) (data)
- "[DNET:SUPPORTED] ~S is not a known DNET." data)
- (assert (sm:gets 'dnet rules) (rules)
- "[DNET:SUPPORTED] ~S is not a known DNET." rules)
- (if *rule-trace* (format *rule-trace* "~&---------- Call to SUPPORTED:"))
- (retrieve-bindings goal data rules nil))
-
- (defun RETRIEVE (goal data rules)
- "retrieve <goal> <data> <rules> [Function]
- Given <goal> is a pattern, returns two values: a list of expressions
- satisfying the pattern, and a list of bindings by which each expression
- unifies with <goal>."
- (declare (inline retrieve-bindings))
- (check-type data symbol)
- (check-type rules symbol)
- (assert (sm:gets 'dnet data) (data)
- "[DNET:RETRIEVE] ~S is not a known DNET." data)
- (assert (sm:gets 'dnet rules) (rules)
- "[DNET:RETRIEVE] ~S is not a known DNET." rules)
- (if *rule-trace* (format *rule-trace* "~&---------- Call to RETRIEVE:"))
- (let ((bindings nil) (results nil))
- (declare (list bindings results))
- ;; Get bindings for all ways the goal is met.
- (dolist (binding-set (retrieve-bindings goal data rules nil))
- (declare (list binding-set))
- ;; Substitute unique bindings into goal to get results.
- (unless (member binding-set bindings :test #'equal)
- (push (substitute-bindings binding-set goal) results)
- (push binding-set bindings)))
- (values results bindings)))
-
- (defvar *SUPPORT-TREE* nil
- "SUPPORT stashes the most recent root TRJ-NODE here, for debugging.")
-
- (defun SUPPORT (goal data rules
- &key (record-failure nil) (include-datum-justification nil))
- "support <goal> <data> <rules>
- &key <record-failure> <include-datum-justification> [Function]
- Given <goal> is a pattern, returns two values: a boolean T or NIL saying
- whether the <goal> is supported, and a TRJ-NODE which is the root of its
- support tree. When the first value is NIL, the partial support tree is
- returned only if <record-failure> is T. If <include-datum-justification>
- is T, the support tree includes justifications recorded in the <data> DNET."
- (declare (inline support-arcs))
- (check-type data symbol)
- (check-type rules symbol)
- (assert (sm:gets 'dnet data) (data)
- "[DNET:SUPPORT] ~S is not a known DNET." data)
- (assert (sm:gets 'dnet rules) (rules)
- "[DNET:SUPPORT] ~S is not a known DNET." rules)
- (if *rule-trace* (format *rule-trace* "~&---------- Call to SUPPORT:"))
- (multiple-value-bind
- (success support-arcs)
- (support-arcs goal data rules record-failure include-datum-justification nil)
- (declare (list support-arcs))
- (values success
- (setq *support-tree*
- (make-trj-node
- :claim goal
- :modality (if success :supported :unsupported)
- :support (if (or success record-failure) support-arcs))))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (provide :rule-back)
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; the end.